﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MediaCenterCommander.RemoteInterfaces;
using System.IO.Ports;
using System.Threading.Tasks;
using System.Globalization;
using System.IO;

namespace MediaCenterCommander.LGTelevision
{
    public class LGTelevisionController : MarshalByRefObject, ITelevisionController, IDisposable
    {
        private object syncObject = new object();

        private string comPort;
        private SerialPort serialPort;
        private Action<string> dataHandler;

        private int maxVolumeLevel = 30;

        private StringBuilder readBuffer = new StringBuilder();

        /// <summary>        
        /// </summary>
        /// <param name="comPort">COM port identifier such as "COM3" or "COM4".</param>
        public LGTelevisionController(string comPort)
        {
            this.comPort = comPort;

            serialPort = new SerialPort(comPort);
            serialPort.Parity = Parity.None;
            serialPort.StopBits = StopBits.One;
            serialPort.DataBits = 8;
            serialPort.Handshake = Handshake.None;
            serialPort.ReadTimeout = 2000;
            serialPort.WriteTimeout = 500;

            serialPort.DtrEnable = true;
            serialPort.RtsEnable = true;

            serialPort.Open();
            serialPort.DataReceived += serialPort_DataReceived;
        }

       
        void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            lock (this.readBuffer)
            {
                SerialPort sp = (SerialPort)this.serialPort;

                byte[] buffer = new byte[4096];
                int bytesRead = sp.Read(buffer, 0, buffer.Length);

                if (bytesRead > 0)
                {
                    for (int co = 0; co < bytesRead; co++)
                    {
                        // LG television responses terminate on "x".
                        Char ch = (char)buffer[co];
                        this.readBuffer.Append(ch);

                        if (ch == 'x')
                        {
                            string response = this.readBuffer.ToString();
                            this.readBuffer.Clear();

                            Console.WriteLine("Response: " + response);

                            if (this.dataHandler != null)
                            {
                                this.dataHandler(response);
                                this.dataHandler = null;
                            }
                        }
                    }
                }
            }            
        }

        public void Dispose()
        {
            this.serialPort.Close();
            this.serialPort = null;
        }

        public override object InitializeLifetimeService()
        {
            return null;
        }

        public bool IsPoweredOn
        {
            get 
            {                
                string response = ExecCommandAndAwaitResponse("ka 01 ff");
                int? responseCode = GetOKHexResponseCode(response);
                if (responseCode != null)
                {
                    if (responseCode.Value > 0)
                        return true;
                    else
                        return false;
                }

                throw new Exception("Unable to determine power state.");                
            }
        }

        public void PowerOn()
        {
            ExecCommandAndAwaitResponse("ka 01 01");            
        }

        public void PowerOff()
        {
            ExecCommandAndAwaitResponse("ka 01 00");            
        }

        public bool IsMuted
        {
            get 
            {
                string response = ExecCommandAndAwaitResponse("ke 01 ff");
                int? responseCode = GetOKHexResponseCode(response);
                if (responseCode != null)
                {
                    if (responseCode.Value > 0)
                        return false;
                    else
                        return true;
                }

                throw new Exception("Unable to determine mute status.");
            }
        }

        public void MuteToggle()
        {            
            ExecCommandAndAwaitResponse("mc 1 09");            
        }

        public void MuteOn()
        {            
            ExecCommandAndAwaitResponse("ke 01 00");            
        }

        public void MuteOff()
        {            
            ExecCommandAndAwaitResponse("ke 01 01");            
        }

        public int VolumeLevel
        {
            get
            {                
                string response = ExecCommandAndAwaitResponse("kf 01 ff");
                int? volumeLevel = GetOKHexResponseCode(response);
                if (volumeLevel != null)
                {                    
                    return volumeLevel.Value;                                                
                }

                throw new Exception("Unable to determine volume level.");                
            }

            set
            {               
                int newVolumeLevel = value;
                if (newVolumeLevel > this.maxVolumeLevel)
                    newVolumeLevel = this.maxVolumeLevel;

                string volumeStr = newVolumeLevel.ToString("X2");
                ExecCommandAndAwaitResponse("kf 01 " + volumeStr);                
            }
        }

        public void VolumeUp()
        {
            lock (this.syncObject)
            {
                ExecCommandAndAwaitResponse("mc 1 02");
            }
        }

        public void VolumeDown()
        {
            lock (this.syncObject)
            {
                ExecCommandAndAwaitResponse("mc 1 03");
            }
        }


        private string ExecCommandAndAwaitResponse(string command, int maxWaitTimeInMilliseconds = 2000)
        {
            lock (this.syncObject)
            {
                var completion = new TaskCompletionSource<string>();

                this.dataHandler = (dataStr) =>
                {
                    completion.SetResult(dataStr);
                };
                this.serialPort.WriteLine(command);

                completion.Task.Wait(maxWaitTimeInMilliseconds);
                if (completion.Task.IsCompleted)
                {
                    string response = completion.Task.Result;
                    completion.Task.Dispose();

                    return response;
                }

                return null;
            }
        }

        private int? GetOKHexResponseCode(string response)
        {
            if (response == null)
                return null;

            int okIndex = response.IndexOf("OK");
            if (okIndex >= 0)
            {
                int startIndex = okIndex + "OK".Length;
                int xIndex = response.IndexOf("x", startIndex);
                string volumeStr = response.Substring(startIndex, xIndex - startIndex);

                int number;
                if (Int32.TryParse(volumeStr, NumberStyles.HexNumber, null, out number))
                {
                    return number;
                }
            }

            return null;
        }
    }
}
